#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>

#include <linux/icmp.h>

#include <stdint.h>
#include <unistd.h>

#include <arpa/inet.h>

#include <poll.h>

// ================================================================================

typedef struct sockaddr_in sockaddr_in_t;
typedef struct sockaddr    sockaddr_t;

// ================================================================================

static uint16_t ip_checksum(uint16_t *data, int len) {
  register int nleft = len;
  register uint16_t answer;
  register uint32_t sum = 0;

  /*
   *  Our algorithm is simple, using a 32 bit accumulator (sum),
   *  we add sequential 16 bit words to it, and at the end, fold
   *  back all the carry bits from the top 16 bits into the lower
   *  16 bits.
   */
  while (nleft > 1)  {
    sum += *data++;
    nleft -= 2;
  }
  
  /*
   * add back carry outs from top 16 bits to low 16 bits
   */
  sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
  sum += (sum >> 16);			/* add carry */
  answer = ~sum;				/* truncate to 16 bits */
  return (answer);
 }

  /* 
 int i;
  uint16_t checksum = 0;

  for (i = 0; i < len/sizeof(__u16); ++i) {
    checksum += data[i];
  }

  return ~checksum;
}
  */

static void usage() {
  printf("Usage: traceroute <Host>\n");
}

int main(int argc, char *argv[]) {
  int probe_sock, icmp_sock;
  sockaddr_in_t dest, host;
  unsigned int ttl;
  struct timeval timeout;
  uint16_t seqn;
  int id;

  static char buf[64];
  static char recv_buf[1024];
  static char control_buf[4096];
  
  if (argc < 2) {
    usage();
    return 1;
  }

  if (inet_aton(argv[1], &dest.sin_addr) < 0) {
    usage();
    return 1;
  }
  dest.sin_family = AF_INET;
  dest.sin_port = 0;
  
  host.sin_family = AF_INET;
  host.sin_port = 0;
  host.sin_addr.s_addr = INADDR_ANY;
  //inet_aton("192.168.216.152", &host.sin_addr);
  icmp_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
  if (icmp_sock == -1) {
    perror("Failed to create an ICMP socket");
    return 1;
  }
  if (bind(icmp_sock, (sockaddr_t*)&host, sizeof(host)) == -1) {
    perror("Failed to bind to the ICMP protocol");
    goto close_icmp;
  }
  if (connect(icmp_sock, (sockaddr_t*)&dest, sizeof(dest)) == -1) {
    perror("Failed to connect to the remote host");
    goto close_icmp;
  }

  memset(buf, 'A', sizeof(buf));

  struct icmp_filter flt;
  flt.data = ~((1<<ICMP_SOURCE_QUENCH)|
               (1<<ICMP_DEST_UNREACH)|
               (1<<ICMP_TIME_EXCEEDED)|
               (1<<ICMP_PARAMETERPROB)|
               (1<<ICMP_REDIRECT)|
               (1<<ICMP_ECHOREPLY));
  if (setsockopt(icmp_sock, SOL_RAW, ICMP_FILTER, &flt, sizeof(flt))) {
    perror("Failed to set an ICMP filter");
  }

  
  timeout.tv_sec = 0;
  timeout.tv_usec = 100000;
  setsockopt(icmp_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
  setsockopt(icmp_sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));

  int val = 1;
  setsockopt(icmp_sock, SOL_IP, IP_RECVERR, &val, sizeof(val));
  
  val = 324;
  setsockopt(icmp_sock, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val));
  val = 131072;  
  setsockopt(icmp_sock, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val));
  
  ttl = 1;
  seqn = 0;
  id = getpid() & 0xFFFF;
  for (;;) {
    struct icmphdr* icmp_msg = (struct icmphdr*)buf;
    struct msghdr msg;
    struct iovec  iov;

  next_probe:

    seqn++;        
        
    icmp_msg->type = ICMP_ECHO;
    icmp_msg->code = 0;
    icmp_msg->un.echo.id = id;
    icmp_msg->un.echo.sequence = htons(seqn);
    icmp_msg->checksum = 0;
    icmp_msg->checksum = ip_checksum(buf, sizeof(buf));

    printf("Sending ECHO request #%d\n", seqn);
    if (send(icmp_sock, buf, sizeof(buf), 0) == -1) {
      perror("Failed to send an ECHO Request\n");
      goto close_icmp;
    }

    usleep(1000000);
    
    if (recv(icmp_sock, recv_buf, sizeof(recv_buf), 0) == -1) {
      perror("Failed to receive an ECHO response");
      continue;
    }

    struct iphdr* iph = recv_buf;

    if (iph->protocol == IPPROTO_ICMP) {
      struct icmphdr* response = (struct icmphdr*)(recv_buf + sizeof(struct iphdr) + iph->frag_off);

      if (response->type == ICMP_ECHOREPLY) {
        printf("Received an ECHO Responce #%d\n", htons(response->un.echo.sequence));
        id++;
      }
    }
  }
  
 close_icmp:
  close(icmp_sock);

  return 0;
}
      
      
